﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.SessionState;
using System.Web;
using System.Web.Util;
using System.Configuration;
using System.Data.SqlClient;
using System.Web.Configuration;
using Newtonsoft.Json;
using SimpleInfrastructure.FrameWorks;
using SimpleInfrastructure.Serialize;
using System.IO.Compression;
using SimpleInfrastructure.Sync;

namespace SimpleService.Session
{
    public class RedisSessionStateStore: SessionStateStoreProviderBase
    {
        const int ITEM_SHORT_LENGTH = 7000;
        static string s_configPartitionResolverType;
        static bool s_configCompressionEnabled;
        private static TimeSpan s_timeOut;

        private static readonly string _sessionPrefix = "redis_session";

        static bool s_oneTimeInited;
        private static MyReadWriteSpinLock _spinLock;

        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(name, config);
            if (!s_oneTimeInited)
            {
                _spinLock.AcquireWriterLock();
                try
                {
                    if (!s_oneTimeInited)
                    {
                        OneTimeInit();
                    }
                }
                finally
                {
                    _spinLock.ReleaseWriterLock();
                }
            }
        }

        void OneTimeInit()
        {
            System.Web.Configuration.SessionStateSection config =(System.Web.Configuration.SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");
            if (config != null)
            {
                s_configPartitionResolverType = config.PartitionResolverType;
                s_configCompressionEnabled = config.CompressionEnabled;
                s_timeOut = config.Timeout;
            }
        }

        //
        // 摘要:
        //     创建要用于当前请求的新 System.Web.SessionState.SessionStateStoreData 对象。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   timeout:
        //     新 System.Web.SessionState.SessionStateStoreData 的会话状态 System.Web.SessionState.HttpSessionState.Timeout
        //     值。
        //
        // 返回结果:
        //     当前请求的新 System.Web.SessionState.SessionStateStoreData。
        public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
        {
            return CreateLegitStoreData(context, null, null, timeout);
        }

        internal static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout)
        {
            if (sessionItems == null)
                sessionItems = new SessionStateItemCollection();
            if (staticObjects == null && context != null)
                staticObjects = SessionStateUtility.GetSessionStaticObjects(context);
            return new SessionStateStoreData(sessionItems, staticObjects, timeout);
        }

        //
        // 摘要:
        //     将新的会话状态项添加到数据存储区中。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的 System.Web.SessionState.HttpSessionState.SessionID。
        //
        //   timeout:
        //     当前请求的会话 System.Web.SessionState.HttpSessionState.Timeout。
        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            byte[] buf;
            int length;
            SerializeStoreData(CreateNewStoreData(context, timeout),
                              ITEM_SHORT_LENGTH, out buf, out length, s_configCompressionEnabled);
            TimeSpan span = TimeSpan.FromMinutes(timeout);
            StackExchangeRedisHelper.SetDirect(GetSessionKey(id), buf, span);
        }
        //
        // 摘要:
        //     释放由 System.Web.SessionState.SessionStateStoreProviderBase 实现使用的所有资源。
        public override void Dispose()
        {
            
        }
        //
        // 摘要:
        //     在请求结束时由 System.Web.SessionState.SessionStateModule 对象调用。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        public override void EndRequest(HttpContext context)
        {
            
        }
        //
        // 摘要:
        //     从会话数据存储区中返回只读会话状态数据。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的 System.Web.SessionState.HttpSessionState.SessionID。
        //
        //   locked:
        //     当此方法返回时，如果请求的会话项在会话数据存储区被锁定，请包含一个设置为 true 的布尔值；否则请包含一个设置为 false 的布尔值。
        //
        //   lockAge:
        //     当此方法返回时，请包含一个设置为会话数据存储区中的项锁定时间的 System.TimeSpan 对象。
        //
        //   lockId:
        //     当此方法返回时，请包含一个设置为当前请求的锁定标识符的对象。有关锁定标识符的详细信息，请参见 System.Web.SessionState.SessionStateStoreProviderBase
        //     类摘要中的“锁定会话存储区数据”。
        //
        //   actions:
        //     当此方法返回时，请包含 System.Web.SessionState.SessionStateActions 值之一，指示当前会话是否为未初始化的无 Cookie
        //     会话。
        //
        // 返回结果:
        //     使用会话数据存储区中的会话值和信息填充的 System.Web.SessionState.SessionStateStoreData。
        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked,
            out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return DoGet(context, id, false, out locked, out lockAge, out lockId, out actions);
        }
        //
        // 摘要:
        //     从会话数据存储区中返回只读会话状态数据。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的 System.Web.SessionState.HttpSessionState.SessionID。
        //
        //   locked:
        //     当此方法返回时，如果成功获得锁定，请包含一个设置为 true 的布尔值；否则请包含一个设置为 false 的布尔值。
        //
        //   lockAge:
        //     当此方法返回时，请包含一个设置为会话数据存储区中的项锁定时间的 System.TimeSpan 对象。
        //
        //   lockId:
        //     当此方法返回时，请包含一个设置为当前请求的锁定标识符的对象。有关锁定标识符的详细信息，请参见 System.Web.SessionState.SessionStateStoreProviderBase
        //     类摘要中的“锁定会话存储区数据”。
        //
        //   actions:
        //     当此方法返回时，请包含 System.Web.SessionState.SessionStateActions 值之一，指示当前会话是否为未初始化的无 Cookie
        //     会话。
        //
        // 返回结果:
        //     使用会话数据存储区中的会话值和信息填充的 System.Web.SessionState.SessionStateStoreData。
        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked,
            out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return DoGet(context, id, true, out locked, out lockAge, out lockId, out actions);
        }

        SessionStateStoreData DoGet(HttpContext context, String id, bool getExclusive,
            out bool locked,
            out TimeSpan lockAge,
            out object lockId,
            out SessionStateActions actions)
        {
            byte[] buf = null;
            MemoryStream stream = null;
            SessionStateStoreData item;

            locked = false;
            lockId = null;
            lockAge = TimeSpan.Zero;
            actions = SessionStateActions.None;

            buf = StackExchangeRedisHelper.GetDirect(GetSessionKey(id));
            using (stream = new MemoryStream(buf))
            {
                item = Deserialize(context,stream);
            }
            return item;
        }

        //
        // 摘要:
        //     由 System.Web.SessionState.SessionStateModule 对象调用，以便进行每次请求初始化。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        public override void InitializeRequest(HttpContext context)
        {
            
        }
        //
        // 摘要:
        //     释放对会话数据存储区中项的锁定。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的会话标识符。
        //
        //   lockId:
        //     当前请求的锁定标识符。
        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            
        }
        //
        // 摘要:
        //     删除会话数据存储区中的项数据。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的会话标识符。
        //
        //   lockId:
        //     当前请求的锁定标识符。
        //
        //   item:
        //     表示将从数据存储区中删除的项的 System.Web.SessionState.SessionStateStoreData。
        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            StackExchangeRedisHelper.Remove(GetSessionKey(id));
        }
        //
        // 摘要:
        //     更新会话数据存储区中的项的到期日期和时间。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的会话标识符。
        public override void ResetItemTimeout(HttpContext context, string id)
        {
            StackExchangeRedisHelper.SetExpire(GetSessionKey(id), s_timeOut);
        }
        //
        // 摘要:
        //     使用当前请求中的值更新会话状态数据存储区中的会话项信息，并清除对数据的锁定。
        //
        // 参数:
        //   context:
        //     当前请求的 System.Web.HttpContext。
        //
        //   id:
        //     当前请求的会话标识符。
        //
        //   item:
        //     包含要存储的当前会话值的 System.Web.SessionState.SessionStateStoreData 对象。
        //
        //   lockId:
        //     当前请求的锁定标识符。
        //
        //   newItem:
        //     如果为 true，则将会话项标识为新项；如果为 false，则将会话项标识为现有的项。
        public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item,
            object lockId, bool newItem)
        {
            byte[] buf;
            int length;
            SerializeStoreData(item,ITEM_SHORT_LENGTH, out buf, out length, s_configCompressionEnabled);
            TimeSpan span = TimeSpan.FromMinutes(item.Timeout);
            StackExchangeRedisHelper.SetDirect(GetSessionKey(id), buf, span);
        }
        //
        // 摘要:
        //     设置对 Global.asax 文件中定义的 Session_OnEnd 事件的 System.Web.SessionState.SessionStateItemExpireCallback
        //     委托的引用。
        //
        // 参数:
        //   expireCallback:
        //     对 Global.asax 文件中定义的 Session_OnEnd 事件的 System.Web.SessionState.SessionStateItemExpireCallback
        //     委托。
        //
        // 返回结果:
        //     如果会话状态存储提供程序支持调用 Session_OnEnd 事件，则为 true；否则为 false。
        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return true;
        }

        private string GetSessionKey(string id)
        {
            return string.Format("{0}_{1}",_sessionPrefix , id);
        }

        static internal void SerializeStoreData(SessionStateStoreData item, int initialStreamSize, out byte[] buf, out int length, bool compressionEnabled)
        {
            using (MemoryStream s = new MemoryStream(initialStreamSize))
            {
                Serialize(item, s);
                if (compressionEnabled)
                {
                    byte[] serializedBuffer = s.GetBuffer();
                    int serializedLength = (int)s.Length;
                    // truncate the MemoryStream so we can write the compressed data in it
                    s.SetLength(0);
                    // compress the serialized bytes
                    using (DeflateStream zipStream = new DeflateStream(s, CompressionMode.Compress, true))
                    {
                        zipStream.Write(serializedBuffer, 0, serializedLength);
                    }
                    // if the session state tables have ANSI_PADDING disabled, last )s are trimmed.
                    // This shouldn't happen, but to be sure, we are padding with an extra byte
                    s.WriteByte((byte)0xff);
                }
                buf = s.GetBuffer();
                length = (int)s.Length;
            }
        }
        internal static void Serialize(SessionStateStoreData item, Stream stream)
        {
            bool hasItems = true;
            bool hasStaticObjects = true;

            BinaryWriter writer = new BinaryWriter(stream);
            writer.Write(item.Timeout);

            if (item.Items == null || item.Items.Count == 0)
            {
                hasItems = false;
            }
            writer.Write(hasItems);

            if (item.StaticObjects == null || item.StaticObjects.NeverAccessed)
            {
                hasStaticObjects = false;
            }
            writer.Write(hasStaticObjects);

            if (hasItems)
            {
                ((SessionStateItemCollection)item.Items).Serialize(writer);
            }

            if (hasStaticObjects)
            {
                item.StaticObjects.Serialize(writer);
            }

            // Prevent truncation of the stream
            writer.Write(unchecked((byte)0xff));
        }
        static internal SessionStateStoreData DeserializeStoreData(HttpContext context, Stream stream, bool compressionEnabled)
        {
            if (compressionEnabled)
            {
                // apply the compression decorator on top of the stream
                // the data should not be bigger than 4GB - compression doesn't work for more than that
                using (DeflateStream zipStream = new DeflateStream(stream, CompressionMode.Decompress, true))
                {
                    return Deserialize(context, zipStream);
                }
            }
            return Deserialize(context, stream);
        }

        internal static SessionStateStoreData Deserialize(HttpContext context, Stream stream)
        {

            int timeout;
            SessionStateItemCollection sessionItems;
            bool hasItems;
            bool hasStaticObjects;
            HttpStaticObjectsCollection staticObjects;
            Byte eof;
            
            try
            {
                BinaryReader reader = new BinaryReader(stream);
                timeout = reader.ReadInt32();
                hasItems = reader.ReadBoolean();
                hasStaticObjects = reader.ReadBoolean();

                if (hasItems)
                {
                    sessionItems = SessionStateItemCollection.Deserialize(reader);
                }
                else
                {
                    sessionItems = new SessionStateItemCollection();
                }

                if (hasStaticObjects)
                {
                    staticObjects = HttpStaticObjectsCollection.Deserialize(reader);
                }
                else
                {
                    staticObjects = SessionStateUtility.GetSessionStaticObjects(context);
                }

                eof = reader.ReadByte();
                if (eof != 0xff)
                {
                    throw new HttpException("invalid session");
                }
            }
            catch (EndOfStreamException)
            {
                throw new HttpException("invalid session");
            }

            return new SessionStateStoreData(sessionItems, staticObjects, timeout);
        }
    }
    
}
